package com.zh.junit.util;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.executable.ExecutableValidator;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 表单验证器工具类，支持手动验证参数对象，类似@Valid注解的功能
 * <p>
 * 支持：
 * - 集合类型验证
 * - 嵌套对象验证（需要@Valid注解）
 * - 忽略null字段验证
 * - 分组验证功能
 *
 * @author ZH
 * @date 9:41 2021-01-22
 */
public final class FormValidator {
    private FormValidator() {
    }

    static final Validator validator;
    static final ExecutableValidator executableValidator;

    static {
        ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .messageInterpolator(new ParameterMessageInterpolator())
                .failFast(false)
                .buildValidatorFactory();
        validator = factory.getValidator();
        executableValidator = factory.getValidator().forExecutables();
    }

    /**
     * 按指定分组验证对象，不忽略null字段
     *
     * @param form   待验证的对象
     * @param groups 验证分组
     * @throws IllegalArgumentException 验证失败时抛出异常
     */
    public static void valid(Object form, Class<?>... groups) throws IllegalArgumentException {
        formValid(form, false, groups);
    }

    /**
     * 按指定分组验证对象，忽略null字段
     *
     * @param form   待验证的对象
     * @param groups 验证分组
     * @throws IllegalArgumentException 验证失败时抛出异常
     */
    public static void validIgnoreNull(Object form, Class<?>... groups) throws IllegalArgumentException {
        formValid(form, true, groups);
    }

    /**
     * 内部验证方法
     */
    private static void formValid(Object form, boolean ignoreNull, Class<?>... groups) throws IllegalArgumentException {
        // 跳过简单属性的验证
        if (form == null || BeanUtils.isSimpleProperty(form.getClass())) {
            return;
        }
        // 跳过数组、Map和注解类型的验证
        if (form.getClass().isArray() || Map.class.isAssignableFrom(form.getClass()) || form.getClass().isAnnotation()) {
            return;
        }

        Set<ConstraintViolation<Object>> result;

        if (form instanceof Collection) {
            result = ((Collection<?>) form).stream()
                    .flatMap(e -> doValidate(e, ignoreNull, groups).stream())
                    .collect(Collectors.toSet());
        } else {
            result = doValidate(form, ignoreNull, groups);
        }

        if (!result.isEmpty()) {
            throw new IllegalArgumentException(formatViolations(result));
        }
    }

    // 执行具体的验证逻辑
    private static Set<ConstraintViolation<Object>> doValidate(Object form, boolean ignoreNull, Class<?>... groups) {
        // 如果是基本类型或简单类型，不需要验证
        if (form.getClass().isPrimitive() || BeanUtils.isSimpleProperty(form.getClass())) {
            return Collections.emptySet();
        }

        Set<ConstraintViolation<Object>> result;
        if (groups == null || groups.length == 0) {
            // 如果没有指定分组，使用默认验证
            result = validator.validate(form);
        } else {
            // 使用指定分组进行验证
            result = validator.validate(form, groups);
        }

        // 忽略为null值的field，即不校验
        if (ignoreNull) {
            Class<?> clazz = form.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();

            Set<String> nullFieldNames = Arrays.stream(declaredFields).filter(field -> {
                field.setAccessible(true);
                try {
                    Object value = field.get(form);
                    return value == null;
                } catch (IllegalAccessException ex) {
                    return false;
                }
            }).map(Field::getName).collect(Collectors.toSet());

            result.removeIf(e -> {
                String propertyName = e.getPropertyPath().toString();
                return nullFieldNames.contains(propertyName);
            });
        }

        return result;
    }

    // 格式化验证违规信息
    private static String formatViolations(Set<ConstraintViolation<Object>> violations) {
        StringBuilder stringBuilder = new StringBuilder();
        violations.forEach(e -> stringBuilder.append(e.getMessage()).append("; "));
        return stringBuilder.toString();
    }
}
